/*
 * $Id: EnvironmentCompiler.java 1259 2007-01-09 14:23:47Z tcoupaye $
 *
 * Behavior Protocols extensions for static and runtime checking
 * developed for the Julia implementation of Fractal.
 *
 * Copyright (C) 2006
 *    Formal Methods In Software Engineering Group
 *    Institute of Computer Science
 *    Academy of Sciences of the Czech Republic
 *
 * Copyright (C) 2006 France Telecom
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
 *
 * Contact: ft@nenya.ms.mff.cuni.cz
 * Authors: Pavel Parizek <parizek@nenya.ms.mff.cuni.cz>
 * 
 */
package org.objectweb.fractal.bpc.adl;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.HashMap;

import org.objectweb.deployment.scheduling.component.api.InstanceProviderTask;
import org.objectweb.deployment.scheduling.component.lib.AbstractConfigurationTask;
import org.objectweb.fractal.adl.ADLException;
import org.objectweb.fractal.adl.Node;
import org.objectweb.fractal.adl.TaskMap;
import org.objectweb.fractal.adl.components.ComponentContainer;
import org.objectweb.fractal.adl.components.PrimitiveCompiler;
import org.objectweb.fractal.api.control.BindingController;

/**
 * A {@link PrimitiveCompiler} to compile {@link Environment} nodes in definitions.  
 */
public class EnvironmentCompiler implements PrimitiveCompiler, BindingController {
 
  /**
   * Name of the mandatory interface bound to the {@link EnvironmentBuilder} used 
   * by this compiler.
   */
  public final static String BUILDER_BINDING = "builder";

  /**
   * The {@link EnvironmentBuilder} used by this compiler.
   */
  private EnvironmentBuilder builder; // the builder client interface
  
  // --------------------------------------------------------------------------
  // Implementation of the BindingController interface
  // --------------------------------------------------------------------------
  
  public String[] listFc() {
    return new String[] { BUILDER_BINDING };
  }

  public Object lookupFc (final String itf) {
    if (itf.equals(BUILDER_BINDING)) {
      return builder;
    }
    return null;
  }

  public void bindFc (final String itf, final Object value) {
    if (itf.equals(BUILDER_BINDING)) {
      builder = (EnvironmentBuilder) value;
    }
  }

  public void unbindFc (final String itf) {
    if (itf.equals(BUILDER_BINDING)) {
      builder = null;
    }
  }
  
 
  // --------------------------------------------------------------------------
  // Implementation of the Compiler interface
  // --------------------------------------------------------------------------
   
  public void compile (List path, ComponentContainer container, TaskMap tasks, Map context)
    throws ADLException
  {
    if (container instanceof EnvironmentContainer) 
    {
      Environment env = ((EnvironmentContainer) container).getEnvironment();
      
      if (env == null) return;
      
      InstanceProviderTask createTask = (InstanceProviderTask) tasks.getTask("create", container);
    
      String valueSetsClass = null;
      String userStubCode = null;
      Map userDriversCode = new HashMap();
	  String protocol = null;
	  Map itfStubsMap = new HashMap();
	  
      if (env.getValueSets() == null) throw new ADLException("The 'valuesets' subelement has to be defined.", (Node) env);
      if (env.getValueSets().getClassName() == null) throw new ADLException("The 'classname' attribute has to be set.", (Node) env);
      valueSetsClass = env.getValueSets().getClassName();
    
      if ((env.getUserStub() != null) && (env.getUserStub().getFile() == null)) throw new ADLException("The 'file' attribute has to be set.", (Node) env);
	  if (env.getUserStub() != null)
	  {
      	userStubCode = loadFileFromClassPath(env.getUserStub().getFile(), (Node) env);
	  }
      
      UserDriver[] userDrivers = env.getUserDrivers();
      if (userDrivers != null)
      {
        for (int i = 0; i < userDrivers.length; i++)
        {
          UserDriver ud = userDrivers[i];
          
          if (ud.getEvent() == null) throw new ADLException("The 'event' attribute has to be set.", (Node) env);
          if (ud.getFile() == null) throw new ADLException("The 'file' attribute has to be set.", (Node) env);
          
          userDriversCode.put(ud.getEvent(), loadFileFromClassPath(ud.getFile(), (Node) env));
        }
      }
	  
	  if (env.getProtocol() != null)
	  {
		if ((env.getProtocol().getValue() == null) && (env.getProtocol().getFile() == null)) throw new ADLException("Either the 'value' or 'file' attribute has to be set.", (Node) env);
        else if ((env.getProtocol().getValue() != null) && (env.getProtocol().getFile() != null)) throw new ADLException("Only one of the 'value' and 'file' attributes may be set.", (Node) env);
        else if (env.getProtocol().getValue() != null) 
		{
          protocol = env.getProtocol().getValue();
		}
        else
		{
          protocol = loadFileFromClassPath(env.getProtocol().getFile(), (Node) env);
		}
	  }
	  
	  ItfStub[] itfStubsArray = env.getItfStubs();
      if (itfStubsArray != null)
      {
        for (int i = 0; i < itfStubsArray.length; i++)
        {
          ItfStub is = itfStubsArray[i];
          
          if (is.getName() == null) throw new ADLException("The 'name' attribute has to be set.", (Node) env);
          if (is.getClassName() == null) throw new ADLException("The 'classname' attribute has to be set.", (Node) env);
          
          itfStubsMap.put(is.getName(), is.getClassName());
        }
      }
    
      SetEnvironmentTask task = new SetEnvironmentTask(builder, valueSetsClass, userStubCode, userDriversCode, protocol, itfStubsMap);
      task.setInstanceProviderTask(createTask);
      tasks.addTask("environment", container, task);
    }
  }
 
  private String loadFileFromClassPath(String fileName, Node node) throws ADLException
  {
    try 
    {
        URL fileURL = EnvironmentCompiler.class.getResource("/" + fileName);
        if (fileURL == null) throw new ADLException("Resource not found: '/" + fileName+"'", node);
        
        Reader ir = new InputStreamReader(fileURL.openStream());
        
        StringBuffer outsb = new StringBuffer();
        char[] buffer = new char[1024];
        int charsRead;
        
        while ((charsRead = ir.read(buffer)) != -1) 
        {
            outsb.append(buffer, 0, charsRead);
        }
        
        return outsb.toString();
    } 
    catch (IOException e) 
    {
        throw new ADLException("Cannot read user driver Java code from external file.", node, e);
    }
  }
        
  /**
   * A task assigning an environment to a component.
   */
  static class SetEnvironmentTask extends AbstractConfigurationTask 
  {
    private EnvironmentBuilder builder;
   
    private String valueSetsClass;
    private String userStubCode;
    private Map userDriversCode;
    private String protocol;
	private Map itfStubs;
	
    public SetEnvironmentTask (EnvironmentBuilder builder, String valueSetsClass, String userStubCode, Map userDriversCode, String protocol, Map itfStubs) 
    {
      this.builder = builder;
      this.valueSetsClass = valueSetsClass;
      this.userStubCode = userStubCode;
      this.userDriversCode = userDriversCode;
	  this.protocol = protocol;
	  this.itfStubs = itfStubs;
    }
   
    public void execute (final Object context) throws Exception 
    {
      Object component = getInstanceProviderTask().getResult();
      builder.setEnvironment(component, valueSetsClass, userStubCode, userDriversCode, protocol, itfStubs);
    }

    public Object getResult() 
    { 
      return null; 
    }
    
    public void setResult (Object result) 
    {
    }
  }
}
